{
  "cells": [
    {
      "cell_type": "raw",
      "id": "ce0e08fd",
      "metadata": {},
      "source": [
        "---\n",
        "keywords: [RunnableLambda, LCEL]\n",
        "---"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fbc4bf6e",
      "metadata": {},
      "source": [
        "# How to run custom functions\n",
        "\n",
        ":::info Prerequisites\n",
        "\n",
        "This guide assumes familiarity with the following concepts:\n",
        "\n",
        "- [LangChain Expression Language (LCEL)](/docs/concepts/lcel)\n",
        "- [Chaining runnables](/docs/how_to/sequence/)\n",
        "\n",
        ":::\n",
        "\n",
        "You can use arbitrary functions as [Runnables](https://api.js.langchain.com/classes/langchain_core.runnables.Runnable.html). This is useful for formatting or when you need functionality not provided by other LangChain components, and custom functions used as Runnables are called [`RunnableLambdas`](https://api.js.langchain.com/classes/langchain_core.runnables.RunnableLambda.html).\n",
        "\n",
        "Note that all inputs to these functions need to be a SINGLE argument. If you have a function that accepts multiple arguments, you should write a wrapper that accepts a single dict input and unpacks it into multiple argument.\n",
        "\n",
        "This guide will cover:\n",
        "\n",
        "- How to explicitly create a runnable from a custom function using the `RunnableLambda` constructor\n",
        "- Coercion of custom functions into runnables when used in chains\n",
        "- How to accept and use run metadata in your custom function\n",
        "- How to stream with custom functions by having them return generators\n",
        "\n",
        "## Using the constructor\n",
        "\n",
        "Below, we explicitly wrap our custom logic using a `RunnableLambda` method:\n",
        "\n",
        "```{=mdx}\n",
        "import IntegrationInstallTooltip from \"@mdx_components/integration_install_tooltip.mdx\";\n",
        "import Npm2Yarn from \"@theme/Npm2Yarn\";\n",
        "\n",
        "<IntegrationInstallTooltip></IntegrationInstallTooltip>\n",
        "\n",
        "<Npm2Yarn>\n",
        "  @langchain/openai @langchain/core\n",
        "</Npm2Yarn>\n",
        "```"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "6bb221b3",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "\u001b[32m\"3 squared is \\\\(3^2\\\\), which means multiplying 3 by itself. \\n\"\u001b[39m +\n",
              "  \u001b[32m\"\\n\"\u001b[39m +\n",
              "  \u001b[32m\"\\\\[3^2 = 3 \\\\times 3 = 9\\\\]\\n\"\u001b[39m +\n",
              "  \u001b[32m\"\\n\"\u001b[39m +\n",
              "  \u001b[32m\"So, 3 squared\"\u001b[39m... 6 more characters"
            ]
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "import { StringOutputParser } from \"@langchain/core/output_parsers\";\n",
        "import { ChatPromptTemplate } from \"@langchain/core/prompts\";\n",
        "import { RunnableLambda } from \"@langchain/core/runnables\";\n",
        "import { ChatOpenAI } from \"@langchain/openai\";\n",
        "\n",
        "const lengthFunction = (input: { foo: string }): { length: string } => {\n",
        "  return {\n",
        "    length: input.foo.length.toString(),\n",
        "  };\n",
        "};\n",
        "\n",
        "const model = new ChatOpenAI({ model: \"gpt-4o\" });\n",
        "\n",
        "const prompt = ChatPromptTemplate.fromTemplate(\"What is {length} squared?\");\n",
        "\n",
        "const chain = RunnableLambda.from(lengthFunction)\n",
        "  .pipe(prompt)\n",
        "  .pipe(model)\n",
        "  .pipe(new StringOutputParser());\n",
        "\n",
        "await chain.invoke({ \"foo\": \"bar\" });"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4728ddd9-914d-42ce-ae9b-72c9ce8ec940",
      "metadata": {},
      "source": [
        "## Automatic coercion in chains\n",
        "\n",
        "When using custom functions in chains with [`RunnableSequence.from`](https://api.js.langchain.com/classes/langchain_core.runnables.RunnableSequence.html#from) static method, you can omit the explicit `RunnableLambda` creation and rely on coercion.\n",
        "\n",
        "Here's a simple example with a function that takes the output from the model and returns the first five letters of it:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "5ab39a87",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "\u001b[32m\"Once \"\u001b[39m"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "import { RunnableSequence } from \"@langchain/core/runnables\";\n",
        "\n",
        "const storyPrompt = ChatPromptTemplate.fromTemplate(\"Tell me a short story about {topic}\");\n",
        "\n",
        "const storyModel = new ChatOpenAI({ model: \"gpt-4o\" });\n",
        "\n",
        "const chainWithCoercedFunction = RunnableSequence.from([\n",
        "  storyPrompt,\n",
        "  storyModel,\n",
        "  (input) => input.content.slice(0, 5),\n",
        "]);\n",
        "\n",
        "await chainWithCoercedFunction.invoke({ \"topic\": \"bears\" });"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c9a481d1",
      "metadata": {},
      "source": [
        "Note that we didn't need to wrap the custom function `(input) => input.content.slice(0, 5)` in a `RunnableLambda` method. The custom function is **coerced** into a runnable. See [this section](/docs/how_to/sequence/#coercion) for more information.\n",
        "\n",
        "## Passing run metadata\n",
        "\n",
        "Runnable lambdas can optionally accept a [RunnableConfig](https://api.js.langchain.com/interfaces/langchain_core.runnables.RunnableConfig.html) parameter, which they can use to pass callbacks, tags, and other configuration information to nested runs."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "ff0daf0c-49dd-4d21-9772-e5fa133c5f36",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "{\n",
            "  generations: [\n",
            "    [\n",
            "      {\n",
            "        text: \"oof\",\n",
            "        message: AIMessage {\n",
            "          lc_serializable: true,\n",
            "          lc_kwargs: [Object],\n",
            "          lc_namespace: [Array],\n",
            "          content: \"oof\",\n",
            "          name: undefined,\n",
            "          additional_kwargs: [Object],\n",
            "          response_metadata: [Object],\n",
            "          tool_calls: [],\n",
            "          invalid_tool_calls: []\n",
            "        },\n",
            "        generationInfo: { finish_reason: \"stop\" }\n",
            "      }\n",
            "    ]\n",
            "  ],\n",
            "  llmOutput: {\n",
            "    tokenUsage: { completionTokens: 2, promptTokens: 13, totalTokens: 15 }\n",
            "  }\n",
            "}\n"
          ]
        }
      ],
      "source": [
        "import { type RunnableConfig } from \"@langchain/core/runnables\";\n",
        "\n",
        "const echo = (text: string, config: RunnableConfig) => {\n",
        "  const prompt = ChatPromptTemplate.fromTemplate(\"Reverse the following text: {text}\");\n",
        "  const model = new ChatOpenAI({ model: \"gpt-4o\" });\n",
        "  const chain = prompt.pipe(model).pipe(new StringOutputParser());\n",
        "  return chain.invoke({ text }, config);\n",
        "};\n",
        "\n",
        "const output = await RunnableLambda.from(echo).invoke(\"foo\", {\n",
        "  tags: [\"my-tag\"],\n",
        "  callbacks: [{\n",
        "    handleLLMEnd: (output) => console.log(output),\n",
        "  }],\n",
        "});"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "922b48bd",
      "metadata": {},
      "source": [
        "# Streaming\n",
        "\n",
        "You can use generator functions (ie. functions that use the `yield` keyword, and behave like iterators) in a chain.\n",
        "\n",
        "The signature of these generators should be `AsyncGenerator<Input> -> AsyncGenerator<Output>`.\n",
        "\n",
        "These are useful for:\n",
        "- implementing a custom output parser\n",
        "- modifying the output of a previous step, while preserving streaming capabilities\n",
        "\n",
        "Here's an example of a custom output parser for comma-separated lists. First, we create a chain that generates such a list as text:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "29f55c38",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "Lion\n",
            ",\n",
            " wolf\n",
            ",\n",
            " tiger\n",
            ",\n",
            " cougar\n",
            ",\n",
            " leopard\n",
            "\n"
          ]
        }
      ],
      "source": [
        "const streamingPrompt = ChatPromptTemplate.fromTemplate(\n",
        "  \"Write a comma-separated list of 5 animals similar to: {animal}. Do not include numbers\"\n",
        ");\n",
        "\n",
        "const strChain = streamingPrompt.pipe(model).pipe(new StringOutputParser());\n",
        "\n",
        "const stream = await strChain.stream({ animal: \"bear\" });\n",
        "\n",
        "for await (const chunk of stream) {\n",
        "  console.log(chunk);\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "46345323",
      "metadata": {},
      "source": [
        "Next, we define a custom function that will aggregate the currently streamed output and yield it when the model generates the next comma in the list:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "f08b8a5b",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[ \"wolf\" ]\n",
            "[ \"lion\" ]\n",
            "[ \"tiger\" ]\n",
            "[ \"cougar\" ]\n",
            "[ \"cheetah\" ]\n"
          ]
        }
      ],
      "source": [
        "// This is a custom parser that splits an iterator of llm tokens\n",
        "// into a list of strings separated by commas\n",
        "async function* splitIntoList(input) {\n",
        "  // hold partial input until we get a comma\n",
        "  let buffer = \"\";\n",
        "  for await (const chunk of input) {\n",
        "    // add current chunk to buffer\n",
        "    buffer += chunk;\n",
        "    // while there are commas in the buffer\n",
        "    while (buffer.includes(\",\")) {\n",
        "      // split buffer on comma\n",
        "      const commaIndex = buffer.indexOf(\",\");\n",
        "      // yield everything before the comma\n",
        "      yield [buffer.slice(0, commaIndex).trim()];\n",
        "      // save the rest for the next iteration\n",
        "      buffer = buffer.slice(commaIndex + 1);\n",
        "    }\n",
        "  }\n",
        "  // yield the last chunk\n",
        "  yield [buffer.trim()];\n",
        "}\n",
        "\n",
        "const listChain = strChain.pipe(splitIntoList);\n",
        "\n",
        "const listChainStream = await listChain.stream({\"animal\": \"bear\"});\n",
        "\n",
        "for await (const chunk of listChainStream) {\n",
        "  console.log(chunk);\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0a5adb69",
      "metadata": {},
      "source": [
        "Invoking it gives a full array of values:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "9ea4ddc6",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "[ \u001b[32m\"lion\"\u001b[39m, \u001b[32m\"tiger\"\u001b[39m, \u001b[32m\"wolf\"\u001b[39m, \u001b[32m\"cougar\"\u001b[39m, \u001b[32m\"jaguar\"\u001b[39m ]"
            ]
          },
          "execution_count": 7,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "await listChain.invoke({\"animal\": \"bear\"})"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3306ac3b",
      "metadata": {},
      "source": [
        "## Next steps\n",
        "\n",
        "Now you've learned a few different ways to use custom logic within your chains, and how to implement streaming.\n",
        "\n",
        "To learn more, see the other how-to guides on runnables in this section."
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Deno",
      "language": "typescript",
      "name": "deno"
    },
    "language_info": {
      "file_extension": ".ts",
      "mimetype": "text/x.typescript",
      "name": "typescript",
      "nb_converter": "script",
      "pygments_lexer": "typescript",
      "version": "5.3.3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
